// THIS IS MAGIC DO NOT TOUCH

#include <input/inputhandler.h>
#include <input/input.h>
#include <base/interface.h>

#include <vector>

//typedef struct _RegisteredInput {
//  unsigned long event;
//  unsigned long id;
//  unsigned long queue;
//} RegisteredInput;

#define REG_SIZE 256

char last_mouse_axis[0x14];
std::vector<std::vector<char> > last_joystick_axis;

std::vector<events::Event *> mouse_reg[REG_SIZE * 2];
std::vector<events::Event *> key_reg[REG_SIZE * 2];
std::vector<std::vector<std::vector<events::Event *> > > joy_reg;

long InitInputHandler() {
  joy_reg.resize(JoystickCount());
  last_joystick_axis.resize(JoystickCount());

  for(unsigned long i = 0; i < 0x14; i++) {
    last_mouse_axis[i] = 0;
  }

  for(unsigned long j = 0; j < joy_reg.size(); j++) {
    joy_reg[j].resize(REG_SIZE * 2);
    last_joystick_axis[j].resize(0x14);
    for(unsigned long i = 0; i < 14; i++) {
      last_joystick_axis[j][i] = 0;
    }
  }
  return 0;
}

long DestroyInputHandler() {
  for(unsigned long i = 0; i < REG_SIZE * 2; i++) {
    for(unsigned long j = 0; j < mouse_reg[i].size(); j++) {
      if(mouse_reg[i][j])
        delete mouse_reg[i][j];
    }
    for(unsigned long j = 0; j < key_reg[i].size(); j++) {
      if(key_reg[i][j])
        delete key_reg[i][j];
    }
  }

  for(unsigned long j = 0; j < joy_reg.size(); j++) {
    for(unsigned long i = 0; i < joy_reg[j].size(); i++) {
      for(unsigned long k = 0; k < joy_reg[j][i].size(); k++) {
        if(joy_reg[j][i][k])
          delete joy_reg[j][i][k];
      }
    }
  }
  return 0;
}

unsigned long InputDataToInputEvent(const InputData * data) {
  unsigned long device = data->device;
  unsigned long event = device;
  long axis_data = data->data->dwData;
  unsigned long state;
  if(device == ID_MOUSE) {
    switch(data->data->dwOfs) {
      case DIMOFS_X:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NXAXIS;
        else
          event |= IE_XAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        event |= state;
        break;
      case DIMOFS_Y:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NYAXIS;
        else
          event |= IE_YAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        event |= state;
        break;
      case DIMOFS_Z:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NZAXIS;
        else
          event |= IE_ZAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        event |= state;
        break;
      default:
        event |= (data->data->dwOfs - DIMOFS_BUTTON0) * 0x0100 + IE_BUTTON(0);
        if(data->data->dwData)
          event |= IA_PRESS;
        else
          event |= IA_RELEASE;
    }
  } else if(device == ID_KEYBOARD) {
    event |= data->data->dwOfs * 0x0100;
    if(data->data->dwData)
      event |= IA_PRESS;
    else
      event |= IA_RELEASE;
  } else {
    switch(data->data->dwOfs) {
      case DIJOFS_X:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NXAXIS;
        else
          event |= IE_XAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        break;
        event |= state;
      case DIJOFS_Y:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NYAXIS;
        else
          event |= IE_YAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        break;
        event |= state;
      case DIJOFS_Z:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NZAXIS;
        else
          event |= IE_ZAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        break;
        event |= state;
      case DIJOFS_RX:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NRXAXIS;
        else
          event |= IE_RXAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        break;
        event |= state;
      case DIJOFS_RY:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NRYAXIS;
        else
          event |= IE_RYAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        break;
        event |= state;
      case DIJOFS_RZ:
        state = IA_PRESS;
        if(axis_data < 0)
          event |= IE_NRZAXIS;
        else
          event |= IE_RZAXIS;
        if(axis_data == 0)
          state = IA_RELEASE;
        event |= state;
        break;
      case DIJOFS_POV(0):
        state = IA_PRESS;
        switch(data->data->dwData) {
          case 0:
            event |= IE_POV_0;
            break;
          case 4500:
            event |= IE_POV_1;
            break;
          case 9000:
            event |= IE_POV_2;
            break;
          case 13500:
            event |= IE_POV_3;
            break;
          case 18000:
            event |= IE_POV_4;
            break;
          case 22500:
            event |= IE_POV_5;
            break;
          case 27000:
            event |= IE_POV_6;
            break;
          case 31500:
            event |= IE_POV_7;
            break;
          default:
            event |= IE_POV_0;
            state = IA_RELEASE;
            break;
        }
        event |= state;
        break;
      default:
        event |= ((data->data->dwOfs - DIJOFS_BUTTON0) * 0x0100 + IE_BUTTON(0));
        if(data->data->dwData)
          event |= IA_PRESS;
        else
          event |= IA_RELEASE;
    }
  }

  return event;
}

void HandleInput() {
  bool doevent = true;
  for(InputData i = NextInput(); i != NO_INPUT_DATA; i = NextInput()) {
    //PrintConsoleLine(InputToString(i));
    if(i.device == 0) {
      switch(i.data->dwOfs) {
      case DIMOFS_X:
        MoveMouseX(i.data->dwData);
        break;
      case DIMOFS_Y:
        MoveMouseY(i.data->dwData);
        break;
      default: ;
      }
    }

    unsigned long input = InputDataToInputEvent(&i);
    unsigned long axis = input & 0xff00;
    unsigned long device = input & 0xff;
    if(device == ID_MOUSE) {
      switch(axis) {
        case IE_NXAXIS:
        case IE_NYAXIS:
        case IE_NZAXIS:
        case IE_NRXAXIS:
        case IE_NRYAXIS:
        case IE_NRZAXIS:
          if(last_mouse_axis[(axis - 0x0100)/0x0100]) {
            last_mouse_axis[(axis - 0x0100)/0x0100] = 0;
            DoEvents(IA_RELEASE | (axis - 0x0100) | ID_MOUSE);
          }
          if(last_mouse_axis[axis / 0X0100]) doevent = false;
          last_mouse_axis[axis / 0X0100] = 1;
          break;
        case IE_XAXIS:
        case IE_YAXIS:
        case IE_ZAXIS:
        case IE_RXAXIS:
        case IE_RYAXIS:
        case IE_RZAXIS:
          if((input & 0xff0000) == IA_RELEASE) {
            if(last_mouse_axis[(axis)/0x0100]) {
              last_mouse_axis[(axis)/0x0100] = 0;
              DoEvents(IA_RELEASE | (axis) | ID_MOUSE);
            }
            if(last_mouse_axis[(axis + 0x0100)/0x0100]) {
              last_mouse_axis[(axis + 0x0100)/0x0100] = 0;
              DoEvents(IA_RELEASE | (axis + 0x0100) | ID_MOUSE);
            }
          } else {
            if(last_mouse_axis[(axis + 0x0100)/0x0100]) {
              last_mouse_axis[(axis + 0x0100)/0x0100] = 0;
              DoEvents(IA_RELEASE | (axis + 0x0100) | ID_MOUSE);
            }
            if(last_mouse_axis[axis / 0X0100]) doevent = false;
            last_mouse_axis[axis / 0X0100] = 1;
          }
          break;
      }
    } else if(device != ID_KEYBOARD) {
      if(axis >= IE_POV_0 && axis <= IE_POV_7) {
        if((input & 0xff0000) != IA_RELEASE)
          last_joystick_axis[device - 2][axis / 0x0100] = 1;
        for(unsigned long j = IE_POV_0; j <= IE_POV_7; j += 0x0100) {
          if(last_joystick_axis[device - 2][j / 0x0100] && (((input & 0xff0000) == IA_RELEASE) || axis != j)) {
            DoEvents(IA_RELEASE | j | device);
            last_joystick_axis[device - 2][j / 0x0100] = 0;
          }
        } 
      } else switch(axis) {
        case IE_NXAXIS:
        case IE_NYAXIS:
        case IE_NZAXIS:
        case IE_NRXAXIS:
        case IE_NRYAXIS:
        case IE_NRZAXIS:
          if(last_joystick_axis[device - 2][(axis - 0x0100)/0x0100]) {
            last_joystick_axis[device - 2][(axis - 0x0100)/0x0100] = 0;
            DoEvents(IA_RELEASE | (axis - 0x0100) | device);
          }
          if(last_joystick_axis[device - 2][axis / 0X0100]) doevent = false;
          last_joystick_axis[device - 2][axis / 0X0100] = 1;
          break;
        case IE_XAXIS:
        case IE_YAXIS:
        case IE_ZAXIS:
        case IE_RXAXIS:
        case IE_RYAXIS:
        case IE_RZAXIS:
          if((input & 0xff0000) == IA_RELEASE) {
            if(last_joystick_axis[device - 2][(axis)/0x0100]) {
              last_joystick_axis[device - 2][(axis)/0x0100] = 0;
              DoEvents(IA_RELEASE | (axis) | device);
            }
            if(last_joystick_axis[device - 2][(axis + 0x0100)/0x0100]) {
              last_joystick_axis[device - 2][(axis + 0x0100)/0x0100] = 0;
              DoEvents(IA_RELEASE | (axis + 0x0100) | device);
            }
          } else {
            if(last_joystick_axis[device - 2][(axis + 0x0100)/0x0100]) {
              last_joystick_axis[device - 2][(axis + 0x0100)/0x0100] = 0;
              DoEvents(IA_RELEASE | (axis + 0x0100) | device);
            }
            if(last_joystick_axis[device - 2][axis / 0X0100]) doevent = false;
            last_joystick_axis[device - 2][axis / 0X0100] = 1;
          }
          break;
      }
    }

    if(doevent) {
      if(axis > IE_POV_7 || (input & 0xff0000) != IA_RELEASE)
        DoEvents(input);
    } else {
      doevent = true;
    }
  }
}

void DoEvents(unsigned long input) {
  unsigned long device = input & 0xff;
  unsigned long id = (input & 0xff00) / 0x0100;
  if(id > 0xff) return;
  unsigned long action = (input & 0xff0000) / 0x010000;
  if(action > 0x01) return;
  unsigned long index = id * 2 + action;

  std::vector<events::Event *> * event_list;

  if(device == ID_MOUSE) {
    event_list = &(mouse_reg[index]);
  } else if(device == ID_KEYBOARD) {
    event_list = &(key_reg[index]);
  } else {
    event_list = &(joy_reg[device - 2][index]);
  }

  for(unsigned long i = 0; i < event_list->size(); i++) {
    events::Event * event = ((*event_list)[i]);
    events::Fire(event->copy());
  }
}

void RegisterInputEvent(unsigned long input, events::Event * event) {
  unsigned long device = input & 0xff;
  unsigned long id = (input & 0xff00) / 0x0100;
  unsigned long action = (input & 0xff0000) / 0x010000;
  unsigned long index = id * 2 + action;
  if(device == ID_MOUSE) {
    mouse_reg[index].push_back(event);
  } else if(device == ID_KEYBOARD) {
    key_reg[index].push_back(event);
  } else {
    joy_reg[device - 2][index].push_back(event);
  }
}
